/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gdbus.h>

#include "connman.h"

#define	_DBG_NOTIFIER(fmt, arg...)	DBG(DBG_NOTIFIER, fmt, ## arg)

static DBusConnection *connection = NULL;

static GSList *notifier_list = NULL;

static gint compare_priority(gconstpointer a, gconstpointer b)
{
	const struct connman_notifier *notifier1 = a;
	const struct connman_notifier *notifier2 = b;

	return notifier2->priority - notifier1->priority;
}

/**
 * connman_notifier_register:
 * @notifier: notifier module
 *
 * Register a new notifier module
 *
 * Returns: %0 on success
 */
int connman_notifier_register(struct connman_notifier *notifier)
{
	_DBG_NOTIFIER("notifier %p name %s", notifier, notifier->name);

	/* TODO(sleffler) check return value */
	notifier_list = g_slist_insert_sorted(notifier_list, notifier,
							compare_priority);
	return 0;
}

/**
 * connman_notifier_unregister:
 * @notifier: notifier module
 *
 * Remove a previously registered notifier module
 */
void connman_notifier_unregister(struct connman_notifier *notifier)
{
	_DBG_NOTIFIER("notifier %p name %s", notifier, notifier->name);

	notifier_list = g_slist_remove(notifier_list, notifier);
}

#define MAX_TECHNOLOGIES 10
static volatile gint registered[MAX_TECHNOLOGIES];
static volatile gint enabled[MAX_TECHNOLOGIES];
static volatile gint connected[MAX_TECHNOLOGIES];

static connman_bool_t istracked(enum connman_service_type type)
{
	switch (type) {
	case CONNMAN_SERVICE_TYPE_ETHERNET:
	case CONNMAN_SERVICE_TYPE_WIFI:
	case CONNMAN_SERVICE_TYPE_WIMAX:
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
	case CONNMAN_SERVICE_TYPE_CELLULAR:
		return TRUE;
	default:
		return FALSE;
	}
}

static void append_list(DBusMessageIter *iter, volatile gint array[])
{
	static const char *type_names[] = {
		[CONNMAN_SERVICE_TYPE_ETHERNET]  = "ethernet",
		[CONNMAN_SERVICE_TYPE_WIFI]      = "wifi",
		[CONNMAN_SERVICE_TYPE_WIMAX]     = "wimax",
		[CONNMAN_SERVICE_TYPE_BLUETOOTH] = "bluetooth",
		[CONNMAN_SERVICE_TYPE_CELLULAR]  = "cellular",
		[CONNMAN_SERVICE_TYPE_VPN]       = "vpn",
	};
	int i;

	for (i = 0; i < MAX_TECHNOLOGIES; i++) {
		if (istracked(i) && g_atomic_int_get(&array[i]) > 0)
			dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
			    &type_names[i]);
	}
}

void __connman_notifier_list_registered(DBusMessageIter *iter, void *arg)
{
	append_list(iter, registered);
}

void __connman_notifier_list_enabled(DBusMessageIter *iter, void *arg)
{
	append_list(iter, enabled);
}

void __connman_notifier_list_connected(DBusMessageIter *iter, void *arg)
{
	append_list(iter, connected);
}

static void technology_registered(enum connman_service_type type)
{
	connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "AvailableTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_registered, NULL);
}

void __connman_notifier_register(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_add(&registered[type], 1) == 0)
		technology_registered(type);
}

void __connman_notifier_unregister(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_dec_and_test(&registered[type]) == TRUE) {
		technology_registered(type);
	}
}

static void technology_enabled(enum connman_service_type type,
    connman_bool_t enabled)
{
	GSList *list;

	_DBG_NOTIFIER("type %d enabled %d", type, enabled);

	connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "EnabledTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_enabled, NULL);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->service_enabled != NULL)
			notifier->service_enabled(type, enabled);
	}
}

void __connman_notifier_enable(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_add(&enabled[type], 1) == 0) {
		technology_enabled(type, TRUE);
	}
}

void __connman_notifier_disable(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_dec_and_test(&enabled[type]) == TRUE) {
		technology_enabled(type, FALSE);
	}
}

static void technology_connected(enum connman_service_type type)
{
	connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "ConnectedTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_connected, NULL);
}

void __connman_notifier_connect(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_add(&connected[type], 1) == 0) {
		technology_connected(type);
	}
}

void __connman_notifier_disconnect(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	if (istracked(type) &&
	    g_atomic_int_dec_and_test(&connected[type]) == TRUE) {
		technology_connected(type);
	}
}

void __connman_notifier_offlinemode(connman_bool_t enabled)
{
	GSList *list;

	_DBG_NOTIFIER("enabled %d", enabled);

	connman_dbus_send_property_changed_variant(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "OfflineMode",
	    DBUS_TYPE_BOOLEAN, &enabled);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->offline_mode)
			notifier->offline_mode(enabled);
	}
}

void __connman_notifier_country_changed(const char *country)
{
	GSList *list;

	_DBG_NOTIFIER("country %s", country);

	connman_dbus_send_property_changed_variant(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "Country",
	    DBUS_TYPE_STRING, &country);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->country_changed)
			notifier->country_changed(country);
	}
}

connman_bool_t __connman_notifier_is_enabled(enum connman_service_type type)
{
	_DBG_NOTIFIER("type %d", type);

	return (istracked(type) && g_atomic_int_get(&enabled[type]) > 0);
}

void __connman_notifier_default_changed(struct connman_service *service)
{
	const char *str = connman_service_get_type(service);
	GSList *list;

	_DBG_NOTIFIER("service %p str \"%s\"", service, str);

	connman_dbus_send_property_changed_variant(CONNMAN_MANAGER_PATH,
	    CONNMAN_MANAGER_INTERFACE, "DefaultTechnology",
	    DBUS_TYPE_STRING, &str);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->default_changed != NULL)
			notifier->default_changed(service);
	}
}

void __connman_notifier_service_state_changed(struct connman_service *service)
{
	GSList *list;

	_DBG_NOTIFIER("service %p", service);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->service_state_changed != NULL)
			notifier->service_state_changed(service);
	}
}

void __connman_notifier_system_suspend(void)
{
	GSList *list;

	_DBG_NOTIFIER("");

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->system_suspend != NULL)
			notifier->system_suspend();
	}
}

void __connman_notifier_system_resume(void)
{
	GSList *list;

	_DBG_NOTIFIER("");

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->system_resume != NULL)
			notifier->system_resume();
	}
}

void __connman_notifier_system_shutdown(void)
{
	GSList *list;

	_DBG_NOTIFIER("");

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->system_shutdown != NULL)
			notifier->system_shutdown();
	}
}

void __connman_notifier_profile_push(struct connman_profile *profile)
{
	GSList *list;

	_DBG_NOTIFIER("profile %p", profile);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->profile_push != NULL)
			notifier->profile_push(profile);
	}
}

void __connman_notifier_profile_pop(struct connman_profile *profile)
{
	GSList *list;

	_DBG_NOTIFIER("profile %p", profile);

	for (list = notifier_list; list; list = list->next) {
		struct connman_notifier *notifier = list->data;

		if (notifier->profile_pop != NULL)
			notifier->profile_pop(profile);
	}
}

int __connman_notifier_init(void)
{
	connection = connman_dbus_get_connection();
	return 0;
}

void __connman_notifier_cleanup(void)
{
	dbus_connection_unref(connection);
}
